/**
 * This class provides formatting for a JTextPane that contains the spelled-out number. 
 * @author David Emole
 */
package com.spell;

import java.awt.Color;
import java.util.ArrayList;
import javax.swing.JTextPane;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import static com.spell.SpellConstants.*;

public class TextFormatter {
	/**
	 * Format a JTextPane that conatins a spelled-out number. 
	 * Words like "minus", "point" and "million" are made to stand out.
	 * The functionality is built around this function: 
	 * <code>StyledDocument.setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace)</code>. 
	 * This applies a defined style (AttributeSet) to a range of text in a JTextPane (marked by offset and length).
	 * @param pane The JTextPane to be formatted
	 */
	public static void format(JTextPane pane){
		//First, restore the pane to default formatting
		restoreDefaultFormat(pane);
		
		//Get Text content of pane
		String wholeText = pane.getText();
		
		/* Style Triple words */
		ArrayList<Range> rangeOfTriples = getListOfRanges(wholeText, TRIPLES);
		Style tripleStyle = pane.addStyle("Triple", null);
		StyleConstants.setBold(tripleStyle, true);
		applyStyle(pane, tripleStyle, rangeOfTriples);
		
		/*Style Minus keyword*/
		Range rangeOfMinus = getRangeOfWord(wholeText, MINUS_SPELLING);
		Style minusStyle = pane.addStyle("Minus", null);
		StyleConstants.setForeground(minusStyle, Color.RED);
		StyleConstants.setFontSize(minusStyle, 16);
		applyStyle(pane, minusStyle, rangeOfMinus);
		
		/*Style Invalid keyword*/
		Range rangeOfInvalid = getRangeOfWord(wholeText, INVALID_TAG);
		Style invalidStyle = pane.addStyle("Invalid", null);
		StyleConstants.setForeground(invalidStyle, new Color(255,0,0));
		applyStyle(pane, invalidStyle, rangeOfInvalid);
		
		/*Style Decimal portion*/
		if(wholeText.contains(POINT_SPELLING)){
			Range rangeOfPoint = getRangeOfWord(wholeText, POINT_SPELLING);
			Style pointStyle = pane.addStyle("Point", null);
			StyleConstants.setForeground(pointStyle, new Color(0,200,0));
			StyleConstants.setFontSize(pointStyle, 16);
			applyStyle(pane, pointStyle, rangeOfPoint);

			String decimalPart = wholeText.substring(rangeOfPoint.offset + rangeOfPoint.length + 1);
			Range rangeOfDecimalPart = getRangeOfWord(wholeText, decimalPart);
			Style decimalStyle = pane.addStyle("Decimal", null);
			StyleConstants.setForeground(decimalStyle, new Color(150,150,150));
			StyleConstants.setFontSize(decimalStyle, 16);
			applyStyle(pane, decimalStyle, rangeOfDecimalPart);
		}
	}

	/**
	 * Applies a given style to a list using the applystyle method for each item.
	 */
	private static void applyStyle(JTextPane pane, Style style, ArrayList<Range> ranges) {
		for(Range r : ranges)
			applyStyle(pane, style, r);
	}

	/**
	 * Apply a style to a given range in the JTextpane.
	 * @param pane The Textpane to be edited
	 * @param style The Style to be applied
	 * @param range The Range where the style should be applied. 
	 */
	private static void applyStyle(JTextPane pane, Style style, Range range) {
		pane.getStyledDocument().setCharacterAttributes(range.offset, range.length, style, true);
	}

	/**
	 * Uses the getRangeOfWord method to generate a list of similar occurrences.
	 * @return A list of Range objects that show occurrences of 'words' in 'text'.
	 */
	private static ArrayList<Range> getListOfRanges(String text, String[] words){
		ArrayList<Range> ranges = new ArrayList<Range>();
		
		//For each word in words, find its range in text, 
		//Add the range to the list of ranges.
		for(String word : words)
			ranges.add(getRangeOfWord(text, word));
		
		return ranges;
	}
	
	/**
	 * Find the Range (beginning index and length) of a word in a text.
	 * @param text The text to be searched
	 * @param word The word to be found
	 * @return A Range object representing the occurrence
	 */
	private static Range getRangeOfWord(String text, String word){
			//If the word is either empty 
			//Or word is not found in text, return null
		if(text==null || word==null || word.isEmpty() || !text.contains(word)) 
			return Range.EMPTY;
			
			//Find the occurrence of the word in the text
			int offset = text.indexOf(word);
			int length = word.length();
			return new Range(offset, length);
	}
	
	/**
	 * Restore Textpane to default formatting and removes all new formatting.
	 * @param pane Pane to be restored to default formatting
	 */
	public static void restoreDefaultFormat(JTextPane pane){
		Style defaultStyle = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
		pane.getStyledDocument().setCharacterAttributes(0, pane.getText().length(), defaultStyle, true);
	}
		
	/**
	 * This class represents the range of a word in a text. 
	 * Range is defined by the index where the word begins (offset) 
	 * and the length of the word (length). It makes targeted formatting easier.
	 * @author David Emole
	 */
	private static class Range{
		public static final Range EMPTY = new Range(0,0);
		public int offset=0;
		public int length=0;
		public Range(int offset, int length){
			this.offset = offset;
			this.length = length;
		}
		
		public String toString(){
			return ("offset: " + offset + " length: " + length);
		}
	}
}
